import { isPlainObject } from 'lodash';
import { uiModules } from 'ui/modules';
import { ApplyScopeBindingsProvider } from './apply_scope_bindings';

/**
 * The <render-directive> directive is useful for programaticaly modifying or
 * extending a view. It allows defining the majority of the directives behavior
 * using a "definition" object, which the implementer can obtain from plugins (for instance).
 *
 * The definition object supports the parts of a directive definition that are
 * easy enough to implement without having to hack angular, and does it's best to
 * make sure standard directive life-cycle timing is respected.
 *
 * @param [Object] definition - the external configuration for this directive to assume
 * @param [Function] definition.controller - a constructor used to create the controller for this directive
 * @param [String] definition.controllerAs - a name where the controller should be stored on scope
 * @param [Object] definition.scope - an object defining the binding properties for values read from
 *                                  attributes and bound to $scope. The keys of this object are the
 *                                  local names of $scope properties, and the values are a combination
 *                                  of the binding style (=, @, or &) and the external attribute name.
 *                                  See [the Angular docs]
 *                                  (https://code.angularjs.org/1.4.9/docs/api/ng/service/$compile#-scope-)
 *                                  for more info
 * @param [Object|Function] definition.link - either a post link function or an object with pre and/or
 *                                          post link functions.
 */
uiModules
  .get('kibana')
  .directive('renderDirective', function (Private) {
    const applyScopeBindings = Private(ApplyScopeBindingsProvider);

    return {
      restrict: 'E',
      scope: {
        'definition': '='
      },
      template: function ($el) {
        return $el.html();
      },
      controller: function ($scope, $element, $attrs, $transclude, $injector) {
        if (!$scope.definition) throw new Error('render-directive must have a definition attribute');

        const { controller, controllerAs, scope } = $scope.definition;

        applyScopeBindings(scope, $scope, $attrs);

        if (controller) {
          if (controllerAs) {
            $scope[controllerAs] = this;
          }

          const locals = { $scope, $element, $attrs, $transclude };
          const controllerInstance = $injector.invoke(controller, this, locals) || this;

          if (controllerAs) {
            $scope[controllerAs] = controllerInstance;
          }
        }
      },
      link: {
        pre($scope, $el, $attrs, controller) {
          const { link } = $scope.definition;
          const preLink = isPlainObject(link) ? link.pre : null;
          if (preLink) preLink($scope, $el, $attrs, controller);
        },
        post($scope, $el, $attrs, controller) {
          const { link } = $scope.definition;
          const postLink = isPlainObject(link) ? link.post : link;
          if (postLink) postLink($scope, $el, $attrs, controller);
        },
      }
    };
  });
